package org.truenewx.tnxjee.core.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

import org.apache.http.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.spec.HttpRequestMethod;
import org.truenewx.tnxjee.core.spec.UnsupportedHttpRequestMethodException;
import org.truenewx.tnxjee.core.util.tuple.Binary;
import org.truenewx.tnxjee.core.util.tuple.Binate;

/**
 * Http客户端工具类
 *
 * @author jianglei
 */
public class HttpClientUtil {

    public static final CloseableHttpClient CLIENT = HttpClientBuilder.create().build();

    private HttpClientUtil() {
    }

    public static String execute(HttpUriRequest request, String encoding) throws IOException {
        CloseableHttpResponse response = CLIENT.execute(request);
        return EntityUtils.toString(response.getEntity(), encoding);
    }

    private static CloseableHttpResponse execute(HttpRequestMethod method, String url, Map<String, String> headers,
            Map<String, Object> params, Object body, String encoding) throws Exception {
        HttpRequestBase request;
        String uri = NetUtil.mergeParams(url, params, encoding);
        switch (method) {
            case GET:
                request = new HttpGet(uri);
                break;
            case POST:
                HttpPost post = new HttpPost(uri);
                if (body != null) {
                    post.setEntity(new StringEntity(JsonUtil.toJson(body),
                            ContentType.create(ContentType.APPLICATION_JSON.getMimeType(), encoding)));
                }
                request = post;
                break;
            default:
                throw new UnsupportedHttpRequestMethodException(method);
        }
        if (headers != null) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                request.setHeader(header.getKey(), header.getValue());
            }
        }
        return CLIENT.execute(request);
    }

    public static Binate<Integer, String> request(HttpRequestMethod method, String url, Map<String, String> headers,
            Map<String, Object> params, Object body, String encoding) throws Exception {
        try (CloseableHttpResponse response = execute(method, url, headers, params, body, encoding)) {
            int statusCode = response.getStatusLine().getStatusCode();
            String content;
            // 301和302重定向状态码，将重定向目标地址作为内容返回
            if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                Header header = response.getFirstHeader(HttpHeaders.LOCATION);
                content = header.getValue();
            } else {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
            return new Binary<>(statusCode, content);
        }
    }

    public static Binate<Integer, String> request(HttpRequestMethod method, String url, Map<String, String> headers,
            Map<String, Object> params, String encoding) throws Exception {
        Object body = null;
        if (method == HttpRequestMethod.POST) {
            body = params;
            params = null;
        }
        return request(method, url, headers, params, body, encoding);
    }

    public static Binate<Integer, String> requestByGet(String url, Map<String, String> headers,
            Map<String, Object> params) throws Exception {
        return request(HttpRequestMethod.GET, url, headers, params, null, Strings.ENCODING_UTF8);
    }

    public static Binate<Integer, String> requestByPost(String url, Map<String, String> headers,
            Map<String, Object> params, Object body) throws Exception {
        return request(HttpRequestMethod.POST, url, headers, params, body, Strings.ENCODING_UTF8);
    }

    public static InputStream getImageByPostJson(String url, Map<String, Object> params) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
        try {
            StringEntity entity = new StringEntity(JsonUtil.toJson(params));
            entity.setContentType("image/png"); // png比jpg具有更大的适应性，固定为png
            httpPost.setEntity(entity);
            HttpResponse response = CLIENT.execute(httpPost);
            return response.getEntity().getContent();
        } catch (Exception e) {
            LogUtil.error(HttpClientUtil.class, e);
        }
        return null;
    }

    public static void download(String url, Map<String, Object> params, Map<String, String> headers,
            BiConsumer<HttpEntity, Map<String, String>> consumer) throws IOException {
        try {
            try (CloseableHttpResponse response = execute(HttpRequestMethod.GET, url, headers, params,
                    null, Strings.ENCODING_UTF8)) {
                if (response != null) {
                    StatusLine statusLine = response.getStatusLine();
                    if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
                        Map<String, String> responseHeaders = new HashMap<>();
                        for (Header header : response.getAllHeaders()) {
                            responseHeaders.put(header.getName(), header.getValue());
                        }
                        consumer.accept(response.getEntity(), responseHeaders);
                    } else {
                        LogUtil.error(HttpClientUtil.class,
                                "====== " + statusLine + HttpRequestMethod.GET.name() + Strings.SPACE + url);
                    }
                }
            }
        } catch (Exception e) {
            if (e instanceof IOException) {
                throw (IOException) e;
            }
            Throwable cause = e.getCause();
            if (cause instanceof IOException) {
                throw (IOException) cause;
            }
            LogUtil.error(HttpClientUtil.class, e);
        }
    }

}
